<?xml version="1.0" encoding="UTF-8"?>
<!-- Author: Anthony B. Coates, Miley Watts LLP. -->
<!-- http://www.mileywatts.com/ -->
<!-- Released under the terms of the Mozilla license version 1.1. -->
<!-- http://www.mozilla.org/MPL/MPL-1.1.html -->
<!-- NOTE: does *not* read metamodel datatype properties (attributes) from the metamodel XMI. -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:msg="http://xml.mileywatts.com/ns/2007/iso20022/ontology/intermediate/0.1"
  exclude-result-prefixes="msg">
  <xsl:param name="SchemaOutputDir"/>
  <xsl:param name="NamespacePrefix">msg</xsl:param>
  <xsl:param name="NamespaceUriStart">http://www.example.com/ns/iso20022/message/</xsl:param>
  <xsl:param name="RootElementName">Document</xsl:param>
  <xsl:param name="RootComplexTypeName">MessageDocument</xsl:param>
  <xsl:param name="IsAddAppInfo">true</xsl:param>
  <xsl:output name="schema" method="xml" indent="yes" encoding="UTF-8"/>
  <xsl:key name="TypeByPackageAndName" match="Message | MessageComponent | DataType | PrimitiveType"
    use="concat(@Package, '::', @Name)"/>
  <xsl:key name="DataTypeByName" match="DataType" use="@Name"/>
  <xsl:key name="PrimitiveTypeByName" match="PrimitiveType" use="@Name"/>
  <xsl:template match="/">
    <SchemaGenerationLog>
      <xsl:apply-templates select="/msg:MessageSet/Message[not(@Abstract) or (@Abstract = 'false')]"
      />
    </SchemaGenerationLog>
  </xsl:template>
  <xsl:template match="Message">
    <xsl:variable name="namespaceUri" select="concat($NamespaceUriStart, @Name)"/>
    <xsl:variable name="outputPath" select="concat($SchemaOutputDir, '/', @Name, '.xsd')"/>
    <xsl:result-document href="{$outputPath}" format="schema">
      <xsl:variable name="schemaTypes" as="node()*">
        <xsl:apply-templates select="current()" mode="dependencies"/>
      </xsl:variable>
      <xsl:variable name="distinctSchemaTypes" select="$schemaTypes union ()" as="node()*"/>
      <xs:schema targetNamespace="{$namespaceUri}">
        <xsl:namespace name="{$NamespacePrefix}" select="$namespaceUri"/>
        <xs:element name="{$RootElementName}"
          type="{concat($NamespacePrefix, ':', $RootComplexTypeName)}">
          <xsl:call-template name="add-schema-documentation"/>
        </xs:element>
        <xs:complexType name="{$RootComplexTypeName}">
          <xsl:call-template name="add-schema-documentation"/>
          <xs:sequence>
            <xs:element name="{@Name}" type="{concat($NamespacePrefix, ':', @Name)}">
              <xsl:call-template name="add-schema-documentation"/>
            </xs:element>
          </xs:sequence>
        </xs:complexType>
        <xsl:apply-templates select="$distinctSchemaTypes[self::Message]" mode="schema-type">
          <xsl:sort select="@Name" order="ascending"/>
        </xsl:apply-templates>
        <xsl:apply-templates select="$distinctSchemaTypes[self::MessageComponent]"
          mode="schema-type">
          <xsl:sort select="@Name" order="ascending"/>
        </xsl:apply-templates>
        <xsl:apply-templates select="$distinctSchemaTypes[self::DataType]" mode="schema-type">
          <xsl:sort select="@Name" order="ascending"/>
        </xsl:apply-templates>
        <xsl:apply-templates select="$distinctSchemaTypes[self::PrimitiveType]" mode="schema-type">
          <xsl:sort select="@Name" order="ascending"/>
        </xsl:apply-templates>
        <xsl:call-template name="insert-dependencies">
          <xsl:with-param name="distinctSchemaTypes" select="$distinctSchemaTypes" as="node()*"/>
        </xsl:call-template>
      </xs:schema>
    </xsl:result-document>
  </xsl:template>
  <xsl:template match="Message | MessageComponent" mode="dependencies">
    <xsl:param name="existingDependencies" as="node()*"/>
    <xsl:variable name="currentDefinition" select="current()" as="node()"/>
    <xsl:if test="not($existingDependencies intersect current())">
      <xsl:sequence select="current()"/>
      <xsl:for-each select="BasedOn">
        <xsl:variable name="baseType"
          select="key('TypeByPackageAndName', concat(@Package, '::', @Name))" as="node()?"/>
        <xsl:if test="not($baseType)">
          <xsl:message terminate="yes">!! type not found (BasedOn): <xsl:value-of
              select="concat(@Package, '::', @Name)"/></xsl:message>
        </xsl:if>
        <xsl:for-each select="$baseType">
          <xsl:apply-templates select="current()" mode="dependencies">
            <xsl:with-param name="existingDependencies"
              select="($existingDependencies, $currentDefinition)"/>
          </xsl:apply-templates>
        </xsl:for-each>
      </xsl:for-each>
      <xsl:for-each select="Element">
        <xsl:variable name="elementType"
          select="key('TypeByPackageAndName', concat(@TypePackage, '::', @Type))" as="node()?"/>
        <xsl:if test="not($elementType)">
          <xsl:message terminate="yes">!! type not found (BasedOn): <xsl:value-of
              select="concat(@Package, '::', @Name)"/></xsl:message>
        </xsl:if>
        <xsl:for-each select="$elementType">
          <xsl:apply-templates select="current()" mode="dependencies">
            <xsl:with-param name="existingDependencies"
              select="($existingDependencies, $currentDefinition)"/>
          </xsl:apply-templates>
        </xsl:for-each>
      </xsl:for-each>
    </xsl:if>
  </xsl:template>
  <xsl:template match="DataType | PrimitiveType" mode="dependencies">
    <xsl:sequence select="current()"/>
  </xsl:template>
  <xsl:template match="Message | MessageComponent" mode="schema-type">
    <xs:complexType name="{@Name}">
      <xsl:call-template name="check-abstract"/>
      <xsl:call-template name="add-schema-annotation"/>
      <xsl:choose>
        <xsl:when test="BasedOn">
          <xs:complexContent>
            <xs:extension base="{concat($NamespacePrefix, ':', BasedOn/@Name)}">
              <xs:sequence>
                <xsl:call-template name="add-elements"/>
              </xs:sequence>
            </xs:extension>
          </xs:complexContent>
        </xsl:when>
        <xsl:otherwise>
          <xs:sequence>
            <xsl:call-template name="add-elements"/>
          </xs:sequence>
        </xsl:otherwise>
      </xsl:choose>
    </xs:complexType>
  </xsl:template>
  <xsl:template match="DataType" mode="schema-type">
    <xs:complexType name="{@Name}">
      <xsl:call-template name="add-schema-annotation"/>
      <xsl:choose>
        <xsl:when test="@Name = 'Binary'">
          <xs:simpleContent>
            <xs:extension base="xs:binary">
              <xs:attribute name="mime" type="xs:token" use="required"/>
              <xs:attribute name="mimeScheme" type="xs:anyURI" use="optional"/>
              <xs:attribute name="contentType" use="optional" default="base64Binary">
                <xs:simpleType>
                  <xs:restriction base="xs:token">
                    <xs:enumeration value="base64Binary"/>
                    <xs:enumeration value="hexBinary"/>
                  </xs:restriction>
                </xs:simpleType>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xsl:when>
        <xsl:when test="@Name = 'Identifier'">
          <xs:simpleContent>
            <xs:extension base="xs:normalizedString">
              <xs:attribute name="scheme" type="xs:anyURI" use="optional"/>
            </xs:extension>
          </xs:simpleContent>
        </xsl:when>
        <xsl:when test="@Name = 'Identifiers'">
          <xs:sequence>
            <xs:element name="Identifier" type="msg:Identifier" minOccurs="0" maxOccurs="unbounded"
            />
          </xs:sequence>
        </xsl:when>
        <xsl:when test="@Name = 'Indicator'">
          <xs:simpleContent>
            <xs:extension base="msg:Boolean"/>
          </xs:simpleContent>
        </xsl:when>
        <xsl:when test="@Name = 'Money'">
          <xs:simpleContent>
            <xs:extension base="msg:Value">
              <xs:attribute name="CurrencyCode" use="required">
                <xs:simpleType>
                  <xs:restriction base="xs:token">
                    <xs:length>3</xs:length>
                    <xs:pattern>[A-Z]{3}</xs:pattern>
                  </xs:restriction>
                </xs:simpleType>
              </xs:attribute>
              <xs:attribute name="CurrencyCodeScheme" type="xs:anyURI" use="optional"
                fixed="http://www.example.com/codelist/currency/iso-3-alpha"/>
            </xs:extension>
          </xs:simpleContent>
        </xsl:when>
        <xsl:when test="@Name = 'Picture'">
          <xs:simpleContent>
            <xs:restriction base="msg:Binary">
              <xs:attribute name="mime" type="xs:token" use="required"/>
              <xs:attribute name="mimeScheme" type="xs:anyURI" use="optional"
                default="http://www.example.com/codelist/mime/image-types"/>
              <xs:attribute name="contentType" use="optional" default="base64Binary">
                <xs:simpleType>
                  <xs:restriction base="xs:token">
                    <xs:enumeration value="base64Binary"/>
                    <xs:enumeration value="hexBinary"/>
                  </xs:restriction>
                </xs:simpleType>
              </xs:attribute>
            </xs:restriction>
          </xs:simpleContent>
        </xsl:when>
        <xsl:when test="@Name = 'Quantity'">
          <xs:simpleContent>
            <xs:extension base="msg:Value">
              <xs:attribute name="Unit" type="xs:normalizedString" use="required"/>
              <xs:attribute name="UnitScheme" type="xs:anyURI" use="optional"/>
            </xs:extension>
          </xs:simpleContent>
        </xsl:when>
        <xsl:when test="@Name = 'Sound'">
          <xs:simpleContent>
            <xs:restriction base="msg:Binary">
              <xs:attribute name="mime" type="xs:token" use="required"/>
              <xs:attribute name="mimeScheme" type="xs:anyURI" use="optional"
                default="http://www.example.com/codelist/mime/sound-types"/>
              <xs:attribute name="contentType" use="optional" default="base64Binary">
                <xs:simpleType>
                  <xs:restriction base="xs:token">
                    <xs:enumeration value="base64Binary"/>
                    <xs:enumeration value="hexBinary"/>
                  </xs:restriction>
                </xs:simpleType>
              </xs:attribute>
            </xs:restriction>
          </xs:simpleContent>
        </xsl:when>
        <xsl:when test="@Name = 'Text'">
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute ref="xml:lang" use="optional"/>
            </xs:extension>
          </xs:simpleContent>
        </xsl:when>
        <xsl:when test="@Name = 'Video'">
          <xs:simpleContent>
            <xs:restriction base="msg:Binary">
              <xs:attribute name="mime" type="xs:token" use="required"/>
              <xs:attribute name="mimeScheme" type="xs:anyURI" use="optional"
                default="http://www.example.com/codelist/mime/video-types"/>
              <xs:attribute name="contentType" use="optional" default="base64Binary">
                <xs:simpleType>
                  <xs:restriction base="xs:token">
                    <xs:enumeration value="base64Binary"/>
                    <xs:enumeration value="hexBinary"/>
                  </xs:restriction>
                </xs:simpleType>
              </xs:attribute>
            </xs:restriction>
          </xs:simpleContent>
        </xsl:when>
        <!-- up to here -->
        <xsl:otherwise>
          <xsl:message terminate="no">!! unknown data type: <xsl:value-of select="@Name"/></xsl:message>
          <xsl:comment> TODO: unrecognised data type </xsl:comment>
          <xs:simpleContent>
            <xs:extension base="xs:string"/>
          </xs:simpleContent>
        </xsl:otherwise>
      </xsl:choose>
    </xs:complexType>
  </xsl:template>
  <xsl:template match="PrimitiveType" mode="schema-type">
    <xs:simpleType name="{@Name}">
      <xsl:call-template name="add-schema-annotation"/>
      <xsl:choose>
        <xsl:when test="@Name = 'BasisPoint'">
          <xs:restriction base="msg:Numeric"/>
        </xsl:when>
        <xsl:when test="@Name = 'Boolean'">
          <xs:restriction base="xs:boolean"/>
        </xsl:when>
        <xsl:when test="@Name = 'Code'">
          <xs:restriction base="xs:token"/>
        </xsl:when>
        <xsl:when test="@Name = 'Date'">
          <xs:restriction base="xs:date"/>
        </xsl:when>
        <xsl:when test="@Name = 'DateTime'">
          <xs:restriction base="xs:dateTime"/>
        </xsl:when>
        <xsl:when test="@Name = 'Duration'">
          <xs:restriction base="xs:duration"/>
        </xsl:when>
        <xsl:when test="@Name = 'Name'">
          <xs:restriction base="xs:normalizedString"/>
        </xsl:when>
        <xsl:when test="@Name = 'Numeric'">
          <xs:restriction base="xs:decimal"/>
        </xsl:when>
        <xsl:when test="@Name = 'PerCent'">
          <xs:restriction base="msg:Numeric"/>
        </xsl:when>
        <xsl:when test="@Name = 'Ratio'">
          <xs:restriction base="msg:Numeric"/>
        </xsl:when>
        <xsl:when test="@Name = 'Text'">
          <xsl:message>!! warning - 'Text' used as primitive type</xsl:message>
          <xs:restriction base="xs:string"/>
        </xsl:when>
        <xsl:when test="@Name = 'Time'">
          <xs:restriction base="xs:time"/>
        </xsl:when>
        <xsl:when test="@Name = 'Value'">
          <xs:restriction base="msg:Numeric"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:message terminate="yes">!! unknown primitive type: <xsl:value-of select="@Name"
          /></xsl:message>
        </xsl:otherwise>
      </xsl:choose>
    </xs:simpleType>
  </xsl:template>
  <xsl:template match="*" mode="schema-type">
    <xsl:message terminate="yes">!! Unrecognised schema type: <xsl:value-of
        select="concat(local-name(), ' ', @Package, '::', @Name)"/></xsl:message>
  </xsl:template>
  <xsl:template name="add-schema-annotation">
    <xs:annotation>
      <xsl:if test="Documentation">
        <xs:documentation>
          <xsl:value-of select="Documentation"/>
        </xs:documentation>
      </xsl:if>
      <xsl:if test="$IsAddAppInfo = 'true'">
        <xs:appinfo>
          <xsl:copy>
            <xsl:copy-of select="@*"/>
          </xsl:copy>
        </xs:appinfo>
      </xsl:if>
    </xs:annotation>
  </xsl:template>
  <xsl:template name="add-schema-documentation">
    <xsl:if test="Documentation">
      <xs:annotation>
        <xs:documentation>
          <xsl:value-of select="Documentation"/>
        </xs:documentation>
      </xs:annotation>
    </xsl:if>
  </xsl:template>
  <xsl:template name="check-abstract">
    <xsl:if test="@Abstract and (@Abstract = 'true')">
      <xsl:attribute name="abstract">true</xsl:attribute>
    </xsl:if>
  </xsl:template>
  <xsl:template name="add-elements">
    <xsl:for-each select="Element">
      <xs:element name="{@Name}" type="{concat($NamespacePrefix, ':', @Type)}">
        <xsl:if test="@MinOccurs != '1'">
          <xsl:attribute name="minOccurs" select="@MinOccurs"/>
        </xsl:if>
        <xsl:if test="@MaxOccurs != '1'">
          <xsl:attribute name="maxOccurs" select="@MaxOccurs"/>
        </xsl:if>
        <xsl:call-template name="add-schema-documentation"/>
      </xs:element>
    </xsl:for-each>
  </xsl:template>
  <xsl:template name="insert-dependencies">
    <xsl:param name="distinctSchemaTypes" as="node()*"/>
    <!-- PrimitiveType: Numeric -->
    <xsl:if test="$distinctSchemaTypes[(@Name = 'BasisPoint') or (@Name = 'PerCent') or (@Name = 'Ratio') or (@Name = 'Value')] and not($distinctSchemaTypes[@Name = 'Numeric'])">
      <xsl:variable name="Numeric">
        <PrimitiveType Name="Numeric"/>
      </xsl:variable>
      <xsl:apply-templates select="$Numeric" mode="schema-type"/>
    </xsl:if>
    <!-- DataType: Indicator -->
    <xsl:if test="$distinctSchemaTypes[@Name = 'Indicator'] and not($distinctSchemaTypes[@Name = 'Boolean'])">
      <xsl:variable name="Boolean">
        <PrimitiveType Name="Boolean"/>
      </xsl:variable>
      <xsl:apply-templates select="$Boolean" mode="schema-type"/>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>
